﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIT.Converters;
using System.Globalization;

namespace UIT.Style
{
    public enum UnitType
    {
        /// <summary>
        /// Measurement is in pixels
        /// </summary>
        Pixel = 1,
        /// <summary>
        /// Measurement is in points. A point represents 1/72 of an inch
        /// </summary>
        Point,
        /// <summary>
        /// Measurement is in picas. A pica represents 12 points
        /// </summary>
        Pica,
        /// <summary>
        /// Measurement is in inches
        /// </summary>
        Inch,
        /// <summary>
        /// Measurement is in millimeters
        /// </summary>
        Mm,
        /// <summary>
        /// Measurement is in centimeters
        /// </summary>
        Cm,
        /// <summary>
        /// Measurement is a percentage relative to the parent element.
        /// </summary>
        Percentage,
        /// <summary>
        /// Measurement is relative to the height of the parent element's font
        /// </summary>
        Em,
        /// <summary>
        /// Measurement is relative to the heigh of the lowercase letter x of the parent element's font.
        /// </summary>
        Ex
    }

    /// <summary>
    /// Represents a length measurement.
    /// </summary>
    [System.ComponentModel.TypeConverter(typeof(UnitConverter))]
#if !SILVERLIGHT
    [Serializable]
#endif
    public struct Unit
    {
        internal const int MaxValue = 32767;
        internal const int MinValue = -32768;
        /// <summary>
        /// Represents an empty <see cref="T:System.Web.UI.WebControls.Unit" />. This field is read-only.
        /// </summary>
        public static readonly Unit Empty = default(Unit);
        private readonly UnitType type;
        private readonly double value;
        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Web.UI.WebControls.Unit" /> is empty.
        /// </summary>
        /// <returns>true if the <see cref="T:System.Web.UI.WebControls.Unit" /> is empty; otherwise, false.
        /// </returns>
        public bool IsEmpty
        {
            get
            {
                return this.type == (UnitType)0;
            }
        }
        /// <summary>
        /// Gets the unit type of the <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </summary>
        /// <returns>
        /// One of the <see cref="T:System.Web.UI.WebControls.UnitType" /> enumeration values. The default is <see cref="F:System.Web.UI.WebControls.UnitType.Pixel" />.
        /// </returns>
        public UnitType Type
        {
            get
            {
                if (!this.IsEmpty)
                {
                    return this.type;
                }
                return UnitType.Pixel;
            }
        }
        /// <summary>
        /// Gets the length of the <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </summary>
        /// <returns>
        /// A double-precision floating point number that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </returns>
        public double Value
        {
            get
            {
                return this.value;
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Web.UI.WebControls.Unit" /> structure with the specified 32-bit signed integer.
        /// </summary>
        /// <param name="value">
        /// A 32-bit signed integer that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" /> in pixels. 
        /// </param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="value" /> is not between -32768 and 32767. 
        /// </exception>
        public Unit(int value)
        {
            if (value < -32768 || value > 32767)
            {
                throw new ArgumentOutOfRangeException("value");
            }
            this.value = (double)value;
            this.type = UnitType.Pixel;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Web.UI.WebControls.Unit" /> structure with the specified double precision floating point number.
        /// </summary>
        /// <param name="value">
        /// A double precision floating point number that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" /> in pixels. 
        /// </param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="value" /> is not between -32768 and 32767. 
        /// </exception>
        public Unit(double value)
        {
            if (value < -32768.0 || value > 32767.0)
            {
                throw new ArgumentOutOfRangeException("value");
            }
            this.value = (double)((int)value);
            this.type = UnitType.Pixel;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Web.UI.WebControls.Unit" /> structure with the specified double precision floating point number and <see cref="T:System.Web.UI.WebControls.UnitType" />.
        /// </summary>
        /// <param name="value">
        /// A double precision floating point number that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />. 
        /// </param>
        /// <param name="type">
        /// One of the <see cref="T:System.Web.UI.WebControls.UnitType" /> enumeration values. 
        /// </param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="value" /> is not between -32768 and 32767. 
        /// </exception>
        public Unit(double value, UnitType type)
        {
            if (value < -32768.0 || value > 32767.0)
            {
                throw new ArgumentOutOfRangeException("value");
            }
            if (type == UnitType.Pixel)
            {
                this.value = (double)((int)value);
            }
            else
            {
                this.value = value;
            }
            this.type = type;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Web.UI.WebControls.Unit" /> structure with the specified length.
        /// </summary>
        /// <param name="value">
        /// A string that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />. 
        /// </param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The specified length is not between -32768 and 32767. 
        /// </exception>
        /// <exception cref="T:System.FormatException">
        ///   <paramref name="value" /> is not a valid CSS-compliant unit expression. 
        /// </exception>
        public Unit(string value)
        {
            this = new Unit(value, CultureInfo.CurrentCulture, UnitType.Pixel);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Web.UI.WebControls.Unit" /> structure with the specified length and <see cref="T:System.Globalization.CultureInfo" />.
        /// </summary>
        /// <param name="value">
        /// A string that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />. 
        /// </param>
        /// <param name="culture">
        /// A <see cref="T:System.Globalization.CultureInfo" /> that represents the culture. 
        /// </param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The specified length is not between -32768 and 32767. 
        /// </exception>
        /// <exception cref="T:System.FormatException">
        ///   <paramref name="value" /> is not a valid CSS-compliant unit expression. 
        /// </exception>
        public Unit(string value, CultureInfo culture)
        {
            this = new Unit(value, culture, UnitType.Pixel);
        }
        internal Unit(string value, CultureInfo culture, UnitType defaultType)
        {
            if (string.IsNullOrEmpty(value))
            {
                this.value = 0.0;
                this.type = (UnitType)0;
                return;
            }
            if (culture == null)
            {
                culture = CultureInfo.CurrentCulture;
            }
            string text = value.Trim().ToLower(CultureInfo.InvariantCulture);
            if (text.Length == 4 && text == "auto")
            {
                this.value = double.NegativeInfinity;
                this.type = UnitType.Pixel;
            }
            else
            {
                int length = text.Length;
                int num = -1;
                for (int i = 0; i < length; i++)
                {
                    char c = text[i];
                    if ((c < '0' || c > '9') && c != '-' && c != '.' && c != ',')
                    {
                        break;
                    }
                    num = i;
                }
                if (num == -1)
                {
                    throw new FormatException(string.Format("No digit could be found in {0}", value));
                }
                if (num < length - 1)
                {
                    this.type = Unit.GetTypeFromString(text.Substring(num + 1).Trim());
                }
                else
                {
                    this.type = defaultType;
                }
                string text2 = text.Substring(0, num + 1);
                try
                {
#if !SILVERLIGHT
                    System.ComponentModel.TypeConverter typeConverter = new System.ComponentModel.SingleConverter();
                    this.value = (float)typeConverter.ConvertFromString(null, culture, text2);
#else
				this.value = Convert.ToSingle(text2);
#endif
                    if (this.type == UnitType.Pixel)
                        this.value = (double)((int)this.value);
                }
                catch
                {
                    throw new FormatException(string.Format("UnitParseNumericPart",
                        value,
                        text2,
                        this.type.ToString("G")
                    ));
                }
                if (this.value < double.MinValue || this.value > double.MaxValue)
                    throw new ArgumentOutOfRangeException("value");
            }
        }
        /// <summary>
        /// Returns a hash code for this <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode()
        {
            var h1 = type.GetHashCode();
            var h2 = value.GetHashCode();

            return (h1 << 5) + h1 ^ h2;
        }
        /// <summary>
        /// Compares this <see cref="T:System.Web.UI.WebControls.Unit" /> with the specified object.
        /// </summary>
        /// <returns>true if the <see cref="T:System.Web.UI.WebControls.Unit" /> that this method is called from is equal to the specified object; otherwise, false.
        /// </returns>
        /// <param name="obj">
        /// The object for comparison. 
        /// </param>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Unit))
            {
                return false;
            }
            Unit unit = (Unit)obj;
            return unit.type == this.type && unit.value == this.value;
        }
        /// <summary>
        /// Compares two <see cref="T:System.Web.UI.WebControls.Unit" /> objects to determine whether they are equal.
        /// </summary>
        /// <returns>true if both <see cref="T:System.Web.UI.WebControls.Unit" /> objects are equal; otherwise, false.
        /// </returns>
        /// <param name="left">
        /// The <see cref="T:System.Web.UI.WebControls.Unit" /> on the left side of the operator. 
        /// </param>
        /// <param name="right">
        /// The <see cref="T:System.Web.UI.WebControls.Unit" /> on the right side of the operator. 
        /// </param>
        public static bool operator ==(Unit left, Unit right)
        {
            return left.type == right.type && left.value == right.value;
        }
        /// <summary>
        /// Compares two <see cref="T:System.Web.UI.WebControls.Unit" /> objects to determine whether they are not equal.
        /// </summary>
        /// <returns>true if the <see cref="T:System.Web.UI.WebControls.Unit" /> objects are not equal; otherwise, false.
        /// </returns>
        /// <param name="left">
        /// The <see cref="T:System.Web.UI.WebControls.Unit" /> on the left side of the operator. 
        /// </param>
        /// <param name="right">
        /// The <see cref="T:System.Web.UI.WebControls.Unit" /> on the right side of the operator. 
        /// </param>
        public static bool operator !=(Unit left, Unit right)
        {
            return left.type != right.type || left.value != right.value;
        }
        private static string GetStringFromType(UnitType type)
        {
            switch (type)
            {
                case UnitType.Pixel:
                    {
                        return "px";
                    }
                case UnitType.Point:
                    {
                        return "pt";
                    }
                case UnitType.Pica:
                    {
                        return "pc";
                    }
                case UnitType.Inch:
                    {
                        return "in";
                    }
                case UnitType.Mm:
                    {
                        return "mm";
                    }
                case UnitType.Cm:
                    {
                        return "cm";
                    }
                case UnitType.Percentage:
                    {
                        return "%";
                    }
                case UnitType.Em:
                    {
                        return "em";
                    }
                case UnitType.Ex:
                    {
                        return "ex";
                    }
                default:
                    {
                        return string.Empty;
                    }
            }
        }
        private static UnitType GetTypeFromString(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return UnitType.Pixel;
            }
            if (value.Equals("px"))
            {
                return UnitType.Pixel;
            }
            if (value.Equals("pt"))
            {
                return UnitType.Point;
            }
            if (value.Equals("%"))
            {
                return UnitType.Percentage;
            }
            if (value.Equals("pc"))
            {
                return UnitType.Pica;
            }
            if (value.Equals("in"))
            {
                return UnitType.Inch;
            }
            if (value.Equals("mm"))
            {
                return UnitType.Mm;
            }
            if (value.Equals("cm"))
            {
                return UnitType.Cm;
            }
            if (value.Equals("em"))
            {
                return UnitType.Em;
            }
            if (value.Equals("ex"))
            {
                return UnitType.Ex;
            }
            throw new ArgumentOutOfRangeException("value");
        }
        /// <summary>
        /// Converts the specified string to a <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.UI.WebControls.Unit" /> that represents the specified string.
        /// </returns>
        /// <param name="s">
        /// The string to convert. 
        /// </param>
        public static Unit Parse(string s)
        {
            return new Unit(s, CultureInfo.CurrentCulture);
        }
        /// <summary>
        /// Converts the specified string and <see cref="T:System.Globalization.CultureInfo" /> to a <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.UI.WebControls.Unit" /> that represents the specified string.
        /// </returns>
        /// <param name="s">
        /// The string to convert. 
        /// </param>
        /// <param name="culture">
        /// A <see cref="T:System.Globalization.CultureInfo" /> object that represents the culture. 
        /// </param>
        public static Unit Parse(string s, CultureInfo culture)
        {
            return new Unit(s, culture);
        }
        /// <summary>
        /// Creates a <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Percentage" /> from the specified double-precision floating-point number.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Percentage" /> that represents the length specified by the double-precision floating-point number.
        /// </returns>
        /// <param name="n">
        /// A double-precision floating-point number that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </param>
        public static Unit Percentage(double n)
        {
            return new Unit(n, UnitType.Percentage);
        }
        /// <summary>
        /// Creates a <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Pixel" /> from the specified 32-bit signed integer.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Pixel" /> that represents the length specified by the <paramref name="n" /> parameter.
        /// </returns>
        /// <param name="n">
        /// A 32-bit signed integer that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />. 
        /// </param>
        public static Unit Pixel(int n)
        {
            return new Unit(n);
        }
        /// <summary>
        /// Creates a <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Point" /> from the specified 32-bit signed integer.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.UI.WebControls.Unit" /> of type <see cref="F:System.Web.UI.WebControls.UnitType.Point" /> that represents the length specified by the 32-bit signed integer.
        /// </returns>
        /// <param name="n">
        /// A 32-bit signed integer that represents the length of the <see cref="T:System.Web.UI.WebControls.Unit" />. 
        /// </param>
        public static Unit Point(int n)
        {
            return new Unit((double)n, UnitType.Point);
        }
        /// <summary>
        /// Converts a <see cref="T:System.Web.UI.WebControls.Unit" /> to a <see cref="T:System.String" />.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> that represents this <see cref="T:System.Web.UI.WebControls.Unit" />.
        /// </returns>
        public override string ToString()
        {
            return this.ToString(CultureInfo.CurrentCulture);
        }
        /// <summary>
        /// Converts a <see cref="T:System.Web.UI.WebControls.Unit" /> to a string equivalent in the specified culture.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> represents this <see cref="T:System.Web.UI.WebControls.Unit" /> in the culture specified by <paramref name="culture" />.
        /// </returns>
        /// <param name="culture">
        /// A <see cref="T:System.Globalization.CultureInfo" /> that represents the culture. 
        /// </param>
        public string ToString(CultureInfo culture)
        {
            return this.ToString((IFormatProvider)culture);
        }
        /// <summary>
        /// Converts a <see cref="T:System.Web.UI.WebControls.Unit" /> to a string equivalent using the specified format provider.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> representing this <see cref="T:System.Web.UI.WebControls.Unit" /> in the format specified by <paramref name="formatProvider" />.
        /// </returns>
        /// <param name="formatProvider">
        /// An <see cref="T:System.IFormatProvider" /> interface implementation that supplies culture-specific formatting information.
        /// </param>
        public string ToString(IFormatProvider formatProvider)
        {
            if (this.IsEmpty)
            {
                return string.Empty;
            }
            string str;
            if (this.type == UnitType.Pixel)
            {
                str = ((int)this.value).ToString(formatProvider);
            }
            else
            {
                str = ((float)this.value).ToString(formatProvider);
            }
            return str + Unit.GetStringFromType(this.type);
        }
        public static implicit operator Unit(int n)
        {
            return Unit.Pixel(n);
        }
    }
}
